import { defineComponent, type PropType, ref, watch } from "vue";
import type {
  UploadFile,
  UploadRawFile,
  UploadRequestOptions
} from "element-plus";
import { message } from "@/utils/message";
import { check, chunkUpload, mergeFiles, remove, upload } from "@/api/file";
import { SHA256 } from "@/utils/hash";

export default defineComponent({
  name: "UploadStorage",
  props: {
    value: {
      type: Array<string>,
      default: () => []
    },
    buttonName: {
      type: String,
      default: "上传"
    },
    tip: {
      type: String
    },
    limit: {
      type: Number
    },
    accept: {
      type: String,
      default: ""
    },
    drag: {
      type: Boolean,
      default: false
    },
    disabled: {
      type: Boolean,
      default: false
    },
    useProgress: {
      type: Boolean,
      default: true
    },
    uploadCallback: {
      type: Function as PropType<(fileId: string) => void>,
      default: () => {}
    },
    errorCallback: {
      type: Function as PropType<(err: Error) => void>,
      default: () => {}
    },
    removeCallback: {
      type: Function as PropType<(fileId: string) => void>,
      default: () => {}
    }
  },
  setup(props, { emit, slots, expose }) {
    const val = ref(props.value);
    watch(val, newVal => emit("update:value", newVal), {
      deep: true
    });
    const fileMap = new Map<number, string>();
    const progress = ref(new Map<number, {}>());
    // 分块大小8MB
    const chunkSize = 1024 * 1024 * 8;
    const uploadFile = async (options: UploadRequestOptions) => {
      let file = options.file;
      let fileSize = file.size || 0;
      let fileName = file.name;
      progress.value.set(file.uid, { fileName, p: 0 });
      const sha256 = await SHA256(file);
      const checkId = await check(null, sha256);
      if (checkId) {
        val.value.push(checkId);
        fileMap.set(file.uid, checkId);
        progress.value.set(file.uid, { fileName, p: 100 });
        props.uploadCallback(checkId);
        return;
      }

      if (fileSize <= chunkSize) {
        // 直接上传
        return upload(file)
          .then(res => {
            val.value.push(checkId);
            fileMap.set(file.uid, res);
            progress.value.set(file.uid, { fileName, p: 100 });
            props.uploadCallback(res);
          })
          .catch(err => {
            props.errorCallback(err);
          });
      } else {
        let chunkTotals = Math.ceil(fileSize / chunkSize);
        for (
          let chunkNumber = 0, start = 0;
          chunkNumber < chunkTotals;
          chunkNumber++, start += chunkSize
        ) {
          let end = Math.min(fileSize, start + chunkSize);
          // uploadFile.raw这个是element plus 中 el-upload的自己上传的文件就放在这个raw里面，可以console.log(uploadFile)看一下
          // 加 ？是因为ts语法提示“uploadFile.raw”可能为“未定义”，加了这个就不过有报错了
          const files = file?.slice(start, end);
          const res = await chunkUpload(
            files,
            chunkNumber + 1,
            chunkTotals,
            fileName
          );
          progress.value.set(file.uid, { fileName, p: res });
        }
        return mergeFiles(chunkTotals, fileName)
          .then(res => {
            val.value.push(checkId);
            fileMap.set(file.uid, res);
            props.uploadCallback(res);
          })
          .catch(err => {
            props.errorCallback(err);
          });
      }
    };

    const onRemove = (uploadFile: UploadFile) => {
      let fileId = fileMap.get(uploadFile.uid);
      removeFile(uploadFile.uid, fileId);
    };

    const beforeUpload = (uploadFile: UploadRawFile) => {
      return uploadFile.size !== 0;
    };

    const exceed = () => {
      message(`最多上传${props.limit}个文件`, { type: "warning" });
    };

    const removeFile = (uid: number, fileId: string) => {
      remove(fileId).then(() => {
        val.value = val.value.filter(item => item !== fileId);
        fileMap.delete(uid);
        progress.value.delete(uid);
        props.removeCallback(fileId);
      });
    };

    const removeAllFile = () => {
      fileMap.forEach((v, k) => {
        removeFile(k, v);
      });
    };

    expose({
      removeAllFile
    });

    return () => (
      <>
        <div class="w-full">
          <el-upload
            multiple
            {...props}
            http-request={uploadFile}
            on-remove={onRemove}
            before-upload={beforeUpload}
            on-exceed={exceed}
          >
            {slots.default?.() ?? null}
            {slots.trigger?.() ?? (
              <el-button disabled={props.disabled} type="primary">
                {props.buttonName}
              </el-button>
            )}
          </el-upload>
          {slots.tip?.() ??
            (props.tip && <div class="el-upload__tip">{props.tip}</div>)}
          {slots.file?.() ?? null}
          {Array.from(progress.value).map(([k, v]) => (
            <el-progress key={k} percentage={v["p"]} className="progress" />
          ))}
        </div>
      </>
    );
  }
});
